在掌握哈希函数、数字签名与椭圆曲线密码学等“工具”之后,我们终于迎来了它们的第一次大规模工程实践——比特币。中本聪用仅9页的白皮书,构建了一个不依赖任何中心化中介即可运转的电子现金网络。本章将精读比特币白皮书的核心思想,并深入拆解其UTXO模型与脚本系统——这是理解后续以太坊账户模型、闪电网络甚至Rollup设计的基石。
4.1 比特币白皮书精读:一个点对点的电子现金系统
4.1.1 设计愿景与核心挑战
传统的电子支付建立在“基于信任的模型(trust-based model)”之上:当你用信用卡向朋友转账100元,银行作为可信中介验证你账户有足够余额,并防止你将同一笔钱同时转给两个人。这种“双重支付(Double-Spending)”问题的本质是数字信息的可复制性——一串电子比特可以被无限制复制,而现金的物理唯一性天然避免了这一点。
中本聪在白皮书中提出的命题极其简洁:如何在无需银行的情况下,让电子支付像现金一样不可双花? 他的答案不是更先进的加密算法,而是一种全新的系统架构——用公开广播、时间戳、经济激励和密码学共同编织出一条不可篡改的链式账本。
flowchart LR
A[传统银行] -->|信任中介| B[防止双花验证]
C[比特币网络] -->|公开广播+PoW| D[全网共识确认]
style A fill:#fca5a5
style C fill:#86efac
本节要点:
- 双重支付问题是数字货币必须解决的首要问题。
- 比特币用去中心化的全网共识替代了银行这一单一信任中介。
4.1.2 核心解决思路:时间戳服务器 + 工作量证明 + 最长链
比特币白皮书提出的三大支柱:
- 时间戳服务器(Timestamp Server):将所有已发生的交易打包成块,并为该块盖上时间戳。时间戳通过将区块的哈希值广为发布实现——一个哈希值就是对之前所有交易数据的“指纹”。
- 工作量证明(Proof-of-Work, PoW):每个区块必须包含一个满足特定条件的哈希值(例如,前面有若干个零)。找到这样的哈希值需要反复尝试不同的随机数(nonce),本质上是将计算资源和电力转化为不可伪造的“时间成本证明”。
- 最长链原则(Longest Chain Rule):当网络中两个矿工几乎同时找到新区块时,网络会出现短暂分叉。所有节点遵循“累计工作量最大的链”。只要诚实节点掌握多数算力,攻击者就无法追赶上诚实链。
graph LR
A[交易广播] --> B[内存池 Mempool]
B --> C[矿工打包候选区块]
C --> D[调整 Nonce 计算哈希]
D --> E{H(block) < Target?}
E -->|是| F[广播区块至全网]
E -->|否| D
F --> G[其他节点验证并接龙]
G --> H[形成最长链]
本节要点:
- PoW的本质是用不可逆的经济消耗(算力+电力)为“时间顺序”提供担保。
- 最长链规则让全网在无中心化协调的情况下自动收敛到唯一历史。
4.1.3 最长链的安全分析:追赶概率
白皮书附录给出了一段经典的泊松分布推导。设攻击者拥有全网算力的比例为 ,诚实节点占比为 。当一笔交易被诚实链埋藏了 个区块之后,攻击者从落后 个区块开始追赶的概率服从泊松分布。其简化形式为:
当 且 增大时, 急速下降——这也是比特币“6次确认”惯例的数学根源。
本节要点:
- 安全不是绝对的,而是概率性的。区块确认数越深,被篡改的可能性以指数级衰减。
- 比特币的安全假设本质上是经济安全:攻击需要持续付出比诚实挖矿更高的算力成本。
4.1.4 隐私模型:伪匿名而非绝对匿名
白皮书中对隐私的描述非常务实:“保持公钥匿名(The public keys can be kept anonymous)”。比特币不依赖隐藏交易参与者,而是让每一笔交易使用新的公钥/地址,使得交易图谱难以关联到真实身份。这种模型被称为伪匿名(Pseudonymity),而非密码学意义上的绝对匿名。
然而,十多年后的链上分析技术证明了这一模型的局限:一旦某个地址与交易所提现、KYC认证产生关联,整个资金流图谱很可能被追踪。
本节要点:
- 比特币的隐私来自“地址隔离”,而非加密隐藏。
- 单个地址的重复使用会严重破坏隐私。
4.1.5 激励设计与货币政策
矿工挖出区块后获得的奖励由两部分构成:协议发行的区块奖励(Block Reward) 和交易者自愿支付的交易手续费(Transaction Fee)。白皮书设计了一个总量收敛的发行曲线:初始奖励为50 BTC,每产出21万个区块(约4年)减半一次。
总供应量收玫于:
随着区块奖励趋于零,手续费将成为矿工收入的主要来源,白皮书以此收尾:“一旦预定数量的硬币进入流通,激励机制就可以完全由交易费来支撑,从而完全免受通货膨胀的影响。”
xychart-beta
title "比特币区块奖励减半时间线"
x-axis [2009, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040]
y-axis "区块奖励 (BTC)" 0 --> 60
bar [50, 50, 25, 25, 12.5, 12.5, 6.25, 6.25, 3.125, 3.125, 1.5625]
本节要点:
- 比特币的2100万总量上限是代码中写死的“社会契约”,不可通过软分叉修改。
- 减半机制使比特币的发行速度呈指数衰减,模拟了贵金属的稀缺性。
4.2 交易、UTXO模型与脚本系统
4.2.1 账户余额并不存在——UTXO才是基本单元
很多初学者在接触比特币时首先会问:“我的比特币钱包里到底存了什么?” 答案是:你的钱包并没有存在一个叫做“余额”的数字。你看到的“余额”,实际上只是你的钱包软件扫描整条区块链后,把所有“属于你公钥但尚未被花费的输出”加总的结果。
比特币的基本状态单元叫做 UTXO(Unspent Transaction Output,未花费交易输出)。它有三个明确的生命阶段:
- 创建(Created):一笔交易在某次出块时将其作为输出(Output)产出;
- 可花费(Unspent):属于你的密钥对锁定、尚未被引用;
- 销毁(Spent):在新的交易输入(Input)中被引用并签名验证后,该UTXO从全局UTXO集合中移除,同时生成新的UTXO。
stateDiagram-v2
[*] --> Created: 某笔交易 Output 产⽣
Created --> Unspent: 确认上链
Unspent --> Spent: 被新交易 Input 引用并签名消耗
Spent --> [*]: 从 UtxoSet 中移除
这种模型与日常生活中使用的现金更为相似:你钱包中有1张100元和2张20元——它们不是用一个数字“140元”来代表的,而是由三张具体面值的钞票(UTXO)所组成。当你需要支付130元时,你必须花费100+20这两张“钞票”,并生成一张10元的“找零”UTXO回到你的地址。
本节要点:
- UTXO是比特币账本的“原子单元”。
- 用户的“余额”是本地钱包对属于自身地址的UTXO的求和。
4.2.2 一笔标准交易的输入输出结构
一笔标准的P2PKH(Pay-to-PubKeyHash,付给公钥哈希)交易由以下部分构成:
| 字段 | 说明 |
|---|---|
| 版本号(4字节) | 当前为 0x02000000(版本2) |
| 输入计数(VarInt) | 包含几个输入 |
| Inputs[] | 每个输入包含:引用的前序交易Hash、输出索引vout、解锁脚本(scriptSig)长度与内容、序列号 |
| 输出计数(VarInt) | 包含几个输出 |
| Outputs[] | 每个输出包含:金额(Satoshi,8字节小端)、锁定脚本(scriptPubKey)长度与内容 |
| 锁定时间(4字节) | 0 或区块高度/Unix时间 |
flowchart TD
subgraph Tx[比特币交易]
dir[Version 4B] --> IC[InCount VarInt]
dir --> OC[OutCount VarInt]
dir --> LT[Locktime 4B]
end
IC --> I1[输入1: prevHash + vout + scriptSig + seq]
IC --> I2[输入2: prevHash + vout + scriptSig + seq]
OC --> O1[输出1: 50,000,000 Sat + P2PKH]
OC --> O2[输出2: 1,500,000 Sat + P2PKH 找零]
下面是一段解析原始交易hex的Python脚本:
import struct
from binascii import unhexlify
def parse_varint(data, offset):
first = data[offset]
if first < 0xfd:
return first, offset + 1
elif first == 0xfd:
return struct.unpack('<H', data[offset+1:offset+3])[0], offset + 3
elif first == 0xfe:
return struct.unpack('<I', data[offset+1:offset+5])[0], offset + 5
else:
return struct.unpack('<Q', data[offset+1:offset+9])[0], offset + 9
def parse_tx(tx_hex: str):
data = unhexlify(tx_hex)
offset = 0
version = struct.unpack('<I', data[offset:offset+4])[0]; offset += 4
in_count, offset = parse_varint(data, offset)
inputs = []
for _ in range(in_count):
prev_hash = data[offset:offset+32][::-1].hex(); offset += 32
vout = struct.unpack('<I', data[offset:offset+4])[0]; offset += 4
script_len, offset = parse_varint(data, offset)
script = data[offset:offset+script_len].hex(); offset += script_len
seq = struct.unpack('<I', data[offset:offset+4])[0]; offset += 4
inputs.append({'prev_hash': prev_hash, 'vout': vout, 'scriptSig': script, 'seq': seq})
out_count, offset = parse_varint(data, offset)
outputs = []
for _ in range(out_count):
value = struct.unpack('<Q', data[offset:offset+8])[0]; offset += 8
script_len, offset = parse_varint(data, offset)
script = data[offset:offset+script_len].hex(); offset += script_len
outputs.append({'value_sat': value, 'value_btc': value / 1e8, 'scriptPubKey': script})
locktime = struct.unpack('<I', data[offset:offset+4])[0]
return {'version': version, 'inputs': inputs, 'outputs': outputs, 'locktime': locktime}
# 示例:一笔比特币测试网交易的raw hex片段
raw = "0200000001" + "0" * 64 + "00000000" + "00" + "00" + "ffffffff" + "02" + "00e40b5402000000" + "19" + "76a914" + "89abcdefabbaabbaabbaabbaabbaabbaabbaabbaabba" + "88ac" + "005a620200000000" + "19" + "76a914" + "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdead" + "88ac" + "00000000"
# 注意:以上使用占位符 hex 演示结构,实际运行时需用真实的交易 hex
tx = parse_tx(raw)
print(f"版本: {tx['version']}, 输入: {len(tx['inputs'])}, 输出: {len(tx['outputs'])}")
print(f"输出0: {tx['outputs'][0]['value_btc']} BTC")本节要点:
- 一个Input通过<交易Hash, vout>二元组唯一引用一个具体UTXO。
- 总输入 ≥ 总输出,差额就是矿工手续费。多输入多输出天然实现找零。
4.2.3 脚本系统:基于栈的图灵不完备语言
比特币的脚本系统是一套图灵不完备的、基于栈的语言,刻意为之——否则攻击者可能通过无限循环耗尽全节点的计算资源。
一笔交易的验证需要将解锁脚本(ScriptSig) 与对应的锁定脚本(ScriptPubKey) 拼接后提交给脚本引擎执行。对于最经典的P2PKH:
- 锁定脚本(在UTXO上附着的“锁”):
OP_DUP OP_HASH160
- 解锁脚本(花费时提供的“钥匙”):
拼接后的执行过程是一个标准的逆波兰栈操作序列:
| 步骤 | 栈顶(Top)向下 | 说明 |
|---|---|---|
1. 压入 | sig | 签名入栈 |
2. 压入 | pubKey, sig | 公钥入栈 |
3. OP_DUP 复制 | pubKey, pubKey, sig | 复制栈顶公钥 |
4. OP_HASH160 | hash160(pubKey), pubKey, sig | 对公钥做RIPEMD160(SHA256) |
5. 压入 | pubKeyHash, hash160(pubKey), pubKey, sig | 目标哈希入栈 |
6. OP_EQUALVERIFY | pubKey, sig | 比较两哈希,不一致则脚本失败 |
7. OP_CHECKSIG | true / false | 用验证对交易的签名 |
以下是一个极简的栈虚拟机Python模拟:
import hashlib
from ecdsa import VerifyingKey, SECP256k1, BadSignatureError
OP_DUP = 0x76
OP_HASH160 = 0xa9
OP_EQUALVERIFY = 0x88
OP_CHECKSIG = 0xac
def hash160(data: bytes) -> bytes:
return hashlib.new('ripemd160', hashlib.sha256(data).digest()).digest()
def run_script(script_sig: bytes, script_pub_key: bytes, tx_hash: bytes):
stack = []
code = list(script_sig) + list(script_pub_key)
i = 0
while i < len(code):
op = code[i]
if 0x01 <= op <= 0x4b: # 压入数据指令
length = op
stack.append(bytearray(code[i+1:i+1+length]))
i += 1 + length
elif op == OP_DUP:
stack.append(stack[-1].copy())
i += 1
elif op == OP_HASH160:
stack.append(hash160(bytes(stack.pop())))
i += 1
elif op == OP_EQUALVERIFY:
a, b = stack.pop(), stack.pop()
if a != b:
raise ValueError("OP_EQUALVERIFY failed")
i += 1
elif op == OP_CHECKSIG:
pub_key = bytes(stack.pop())
sig = bytes(stack.pop())
try:
vk = VerifyingKey.from_string(pub_key, curve=SECP256k1)
valid = vk.verify(sig, tx_hash, hashfunc=hashlib.sha256)
stack.append(1 if valid else 0)
except BadSignatureError:
stack.append(0)
i += 1
else:
i += 1
return stack[-1] != 0 if stack else False本节要点:
- 锁定脚本定义“花这笔钱的条件”,解锁脚本满足该条件。
- 比特币有意选择图灵不完备,为整个网络提供可确定性的验证成本上限。
4.2.4 脚本类型的演进:P2PK → P2PKH → P2SH → SegWit → Taproot
比特币的地址格式变迁本身就是一部“协议工程史”:
| 类型 | 地址前缀 | 核心特征 | 升级目的 |
|---|---|---|---|
| P2PK | 无标准地址 | 直接暴露完整公钥 | 早期最简实现 |
| P2PKH | 1... | 收款地址仅含公钥哈希 | 缩短地址、提升隐私 |
| P2SH | 3... | 锁定条件先哈希化 | 支持多重签名、HTLC等复杂合约 |
| P2WPKH/P2WSH | bc1q... (Bech32) | 将签名/脚本隔离到Witness字段 | 修复交易可锻性、降低费用 |
| P2TR (Taproot) | bc1p... (Bech32m) | Schnorr签名 + Merkle化脚本树 | 隐私无区别、签名聚合、扩展性 |
隔离见证(SegWit) 的技术本质是:将 scriptSig 数据从“交易主体”中抽离,存放到 Witness 独立字段中。由于交易ID(txid)不再包含签名数据,它同时修复了交易可锻性(Transaction Malleability) 问题——即攻击者可以修改签名但保持交易语义不变,原txid就会改变,导致依赖txid的链下合约(如闪电网络)失效。
Taproot(P2TR) 则通过引入 Schnorr 签名,将“公钥路径直接花费”和“脚本路径条件花费”在链上表现得完全一致:
其中 为内部公钥。这意味着使用最普通的公钥签名路径支付,与使用复杂脚本条件的支付,对外看起来没有任何区别——极大地提升了复杂合约的隐私性。
本节要点:
- 脚本的演进方向:隐私增强 + 费用优化 + 向前兼容的软分叉升级。
- Taproot让复杂合约在链上看起来和简单付款一样,是协议工程与密码学的精妙结合。
4.2.5 实战:地址编码与PoW模拟
1. Base58Check 地址编码(P2PKH与P2SH)
import hashlib
ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghjkmnopqrstuvwxyz"
def base58_encode(data: bytes) -> str:
num = int.from_bytes(data, 'big')
result = ""
while num > 0:
num, rem = divmod(num, 58)
result = ALPHABET[rem] + result
# 处理前导零字节
leading = 0
for b in data:
if b == 0:
leading += 1
else:
break
return "1" * leading + result
def base58check_encode(version: int, payload: bytes) -> str:
data = bytes([version]) + payload
checksum = hashlib.sha256(hashlib.sha256(data).digest()).digest()[:4]
return base58_encode(data + checksum)
# 示例:从一个假想的公钥哈希生成 P2PKH 地址(主网版本字节 0x00)
pk_hash = bytes([0x00] * 20) # 占位符公钥哈希
p2pkh = base58check_encode(0x00, pk_hash)
print("P2PKH 地址示例格式:", p2pkh)2. 极简PoW挖矿模拟
import hashlib, time, struct
def mine_pow(data_prefix: bytes, target_prefix_zero: int):
target = 1 << (256 - target_prefix_zero * 4) # 约 target_prefix_zero 个十六进制前导零
nonce = 0
start = time.time()
while True:
nonce_bytes = struct.pack('<I', nonce)
attempt = hashlib.sha256(hashlib.sha256(data_prefix + nonce_bytes).digest()).digest()
if int.from_bytes(attempt, 'big') < target:
elapsed = time.time() - start
hashes_per_sec = (nonce + 1) / max(elapsed, 0.0001)
return nonce, attempt.hex(), hashes_per_sec
nonce += 1
# 设置极低的难度(2个前导零),本地秒级出块
data = b"example block header data"
nonce, hash_result, hps = mine_pow(data, 2)
print(f"Found nonce={nonce}, hash={hash_result}, speed={hps:.0f} H/s")本节要点:
- 地址编码是对公钥哈希的包装,Base58Check 和 Bech32 分别服务于兼容性与现代效率需求。
- PoW挖矿的本质是在巨大的搜索空间中寻找满足难度目标的哈希值,随机数是唯一可自由调整的字段。
4.3 挖矿与工作量证明(PoW)
在上一节中,我们构建了比特币区块的基本结构,理解了交易如何被打包、Merkle 树如何压缩,以及区块头如何通过 PrevHash 将整条链串联成一条不可篡改的日志。然而,有一个核心问题尚未回答:在完全开放的 P2P 网络中,任何人都可以声称自己“打包了一个新区块”,凭什么全网要接受你的区块作为合法时序?中本聪给出的答案是:让区块的合法性变得昂贵——只有那些付出了真实物理成本(算力与电力)的人,才获得记账权。这就是工作量证明(Proof of Work,工作量证明)的本质。
4.3.1 挖矿的真实功能:为区块盖上"成本之印"
许多人对“挖矿”存在误解,认为矿工在“凭空印钞票”。但比特币系统并不凭空创造价值,它只是在向完成特定计算任务的人发放奖励。这个计算任务本身没有算术意义上的捷径:找到满足条件的哈希值唯一可靠的方法就是暴力尝试。每一次尝试都在消耗 CPU/GPU/ASIC 的电力和折旧,因此一个被全网接受的区块,实际上携带了一个不可伪造的成本证明(Unforgeable Costliness)。攻击者若想篡改某个历史区块,就必须针对该区块及其后续所有区块重新付出与诚实节点同等甚至更高的物理成本,这在经济上是自我挫败的。
4.3.2 挖矿算法的完整流程
比特币挖矿并非随机操作,而是一套高度结构化的迭代搜索流程:
- 候选区块构造:矿工从内存池(Mempool)中按交易费率排序,选择若干交易,构造 Merkle 树并计算 Merkle 根。
- 组装区块头:包含 4 字节版本号、32 字节前序区块哈希(
PrevHash)、32 字节 Merkle 根、4 字节时间戳、4 字节nBits(难度目标编码)、以及 4 字节Nonce。 - 双重 SHA-256 哈希:对 80 字节区块头执行
SHA-256d(即SHA-256(SHA-256(header)))。 - 目标比较:将得到的 256 位哈希解释为一个大整数,若其严格小于当前网络目标值(Target),则该 Nonce 有效;矿工立即广播该区块;否则,Nonce 加 1,回到第 3 步继续循环。
- 搜索空间扩展:32 位 Nonce 只有约 43 亿种组合,在现代矿机中不到 1 秒即可遍历完毕。因此矿工通过修改 Coinbase 交易中的
extraNonce字段间接改变 Merkle 根,从而扩展搜索空间。
flowchart TD
A[从内存池按费率筛选交易] --> B[构造 Coinbase 交易与 Merkle 根]
B --> C[组装区块头:版本号 + PrevHash + Merkle根 + 时间戳 + nBits + Nonce]
C --> D[计算 SHA-256d(区块头)]
D --> E{哈希值 < 当前 Target?}
E -->|是| F[广播区块到全网,获得出块奖励]
E -->|否| G[修改 Nonce 或 ExtraNonce/时间戳]
G --> D
上图展示了挖矿的完整闭环。请注意,这个流程里没有任何“解密”或“计算难题”需要求解,它本质上是一个在巨大的哈希空间中海选幸运数字的抽奖过程。你的算力越大,每秒能尝试的 Nonce 数量越多,中奖概率也就越高。
4.3.3 难度目标与 nBits 编码
若要在 256 位空间里直接比较哈希大小,区块头存储的 Target 将占用 32 字节。为了节省空间,比特币采用一种紧凑的 4 字节 nBits 编码(类似科学计数法)。将 nBits 按大端解析为高位的 1 字节指数(exponent)与低位的 3 字节系数(coefficient),可展开为 256 位目标值:
通俗地说,"前导零越多,难度越高"的直觉可以精确转化为数学语言:区块头哈希作为大端整数值必须严格小于当前 Target 值。Target 越小,满足条件的哈希在整个空间中占比就越小,寻找难度越大。
Difficulty(难度)与 Target 成反比。定义全网最低难度(创世区块难度)对应的基准目标值为 ,则当前难度可表示为:
意味着与创世区块难度持平。随着全网算力持续增加, Target 不断被下调,当前比特币主网的 已超过 万亿量级。
4.3.4 代码实战:极简 PoW 挖矿演示
下面的 Python 程序演示了 PoW 的核心逻辑:给定一个数据前缀和难度(以二进制前导零位数表示),通过遍历 nonce 寻找满足 SHA-256d(前缀 + nonce) <= target 的解。为了教学演示,这里的难度仅设置为 8 位或 16 位前导零,可在本地秒级完成出块。
import hashlib
import struct
import time
def sha256d(data):
"""双重 SHA-256(SHA-256d),比特币标准哈希函数。"""
return hashlib.sha256(hashlib.sha256(data).digest()).digest()
def mine(data_prefix, zero_bits):
"""
极简 PoW 挖矿演示:寻找 nonce 使 SHA-256d(前缀 + nonce) 的前导零满足难度。
zero_bits: 要求哈希值二进制前导零位数(教学用低难度,秒级出块)。
"""
# 构造目标阈值:哈希整数值需 <= target
target = (1 << (256 - zero_bits)) - 1
nonce = 0
start = time.time()
while True:
# 将 nonce 打包为 4 字节大端整数,类似比特币区块头中的 Nonce 字段
nonce_bytes = struct.pack('>I', nonce)
h = sha256d(data_prefix + nonce_bytes)
h_int = int.from_bytes(h, 'big')
if h_int <= target:
elapsed = time.time() - start
speed = nonce / max(elapsed, 1e-6)
print("找到有效 Nonce!")
print(f" Nonce (十进制) : {nonce}")
print(f" Nonce (十六进制): {nonce_bytes.hex()}")
print(f" 哈希值 : {h.hex()}")
print(f" 遍历次数 : {nonce + 1:,}")
print(f" 耗时 : {elapsed:.4f} 秒")
print(f" 速度 : {speed:,.0f} H/s")
return nonce, h
nonce += 1
if nonce > 0xFFFFFFFF:
raise RuntimeError("Nonce 溢出 32 位范围,未找到解")
if __name__ == "__main__":
# 模拟一个区块头前缀(不含 nonce 部分)
data_prefix = b"Block#42|PrevHash=abcd|TxRoot=ef01|Time=20260101"
print("=== 难度:8 位二进制前导零(约 2 个十六进制前导零) ===")
mine(data_prefix, zero_bits=8)
print()
print("=== 难度:16 位二进制前导零(约 4 个十六进制前导零) ===")
mine(data_prefix, zero_bits=16)示例运行输出:
=== 难度:8 位二进制前导零(约 2 个十六进制前导零) ===
找到有效 Nonce!
Nonce (十进制) : 365
Nonce (十六进制): 0000016d
哈希值 : 001478eab05aaf803f70a0a14377dcb87e643a66ac728c498b989c08ff810771
遍历次数 : 366
耗时 : 0.0005 秒
速度 : 684,363 H/s
=== 难度:16 位二进制前导零(约 4 个十六进制前导零) ===
找到有效 Nonce!
Nonce (十进制) : 7,075
Nonce (十六进制): 00001ba3
哈希值 : 000043d2e6bd9f5b3258d372dfaa8fb7c5fc55f4d010cf601e414159422a1ad1
遍历次数 : 7,076
耗时 : 0.0095 秒
速度 : 741,015 H/s从输出可以直观感受到两个事实:第一,难度的微小增加会使搜索时间成倍增长——每增加 1 个二进制前导零,平均搜索空间就扩大 1 倍;第二,即使是这个本地演示以每秒数十万哈希的速度运算,在真实比特币网络中,全网总算力以每秒数百 ExaHash( 次哈希)计,个人电脑已绝无可能独立出块。
本节要点
- 挖矿的本质不是"印钞",而是通过不可逆的物理资源消耗(算力+电力)为区块打上不可伪造的成本证明,使得篡改历史在经济上不可行。
- 挖矿算法是一个暴力搜索循环:组装区块头 → 双重 SHA-256d 哈希 → 与 Target 比较 → 满足则广播,否则调整 Nonce/ExtraNonce 继续迭代。
nBits是一种紧凑编码,展开公式为 。难度与 Target 成反比:。
4.4 难度调整与出块时间稳定
上一节讲清楚了“怎么挖”和“难度是什么”,但比特币网络面临一个更深层的问题:全网算力并非恒定。今天加入的矿机越多,出块速度就越快;若矿场因电价波动关机,出块又会变慢。如果出块时间忽快忽慢,交易确认时间将不可预期,网络经济行为将陷入混乱。中本聪为此设计了一套难度调整算法(Difficulty Adjustment Algorithm,DAA)。
4.4.1 难度调整算法(DAA)概述
比特币每挖出 2016 个区块(按 10 分钟/区块计算,约等于 2 周),就会触发一次难度调整。协议对比这 2016 个区块的实际总出块时间与预期总时间(),重新计算下一个周期的目标值:
若实际耗时比预期短(算力增强),则 NewTarget 变小,难度增加;若实际耗时比预期长(算力下降),则 NewTarget 变大,难度降低。为了防止算力剧烈波动导致目标值跳跃过大,协议对单次调整设置了上下限:
- 单次难度增加不得超过 4 倍(即 NewTarget 不能低于 )
- 单次难度降低不能超过 0.25 倍(即 NewTarget 不能高于 )
换句话说,难度调整比例被限制在 的区间内。这一限制为协议提供了缓冲,避免了极端情况下的目标值雪崩式变化。
下面的闭环图展示了这个负反馈系统的运作逻辑:
flowchart LR
A[设定 2016 区块评估窗口] --> B[统计实际平均出块时间]
B --> C{实际时间 vs 预期 10 分钟/块}
C -->|实际 < 预期| D[算力增加 → 新 Target 下调]
C -->|实际 > 预期| E[算力减少 → 新 Target 上调]
D --> F[出块难度增大 → 出块时间被拉回 10 分钟]
E --> F
F --> A
这个闭环的核心特征在于它是负反馈机制:无论算力涌入还是流失,协议都会通过调整 Target 将实际出块时间强行拉回 10 分钟的锚定点。
4.4.2 为什么目标出块时间设定为 10 分钟?
有人会问:既然更快的出块意味着更快的交易确认,为什么不把目标设为 1 分钟?答案在于网络传播延迟与分叉概率之间的工程权衡:
- 出块时间过短(如 1 分钟):矿工 A 挖出一个新区块后,需要广播到全网。如果出块时间太短,在区块尚未传播到矿工 B 时,B 可能已经基于旧区块头找到了一个竞争区块。此时网络出现两个有效但互斥的区块,形成分叉(Fork)。分叉频繁会导致大量无效竞争算力浪费,并且用户需要等待更长确认深度才能确信交易不可回滚。
- 出块时间过长(如 1 小时):虽然分叉概率极低,但普通用户完成一笔交易需要等待数小时才能确认,支付体验不可接受。
- 10 分钟:中本聪基于 2008-2009 年互联网骨干带宽与 1 MB 区块大小的现实条件,选择了 10 分钟作为折中点——既给区块传播留出足够裕度,又不至于让确认等待变得不可忍受。
4.4.3 难度调整的历史与现实
比特币的难度调整机制并非一成不变。在其分叉币 比特币现金(BCH) 的早期,为了应对算力剧烈波动,BCH 曾引入紧急难度调整(Emergency Difficulty Adjustment,EDA)机制。然而 EDA 被证明存在博弈缺陷:矿工可以通过“算力跳挖”在 BTC 和 BCH 之间套利,导致 BCH 的出块时间剧烈震荡,稳定性远不如 BTC 的原始 DAA。BCH 后来改用更平滑的难度调整算法来修补这一问题。
而在比特币主网,当前难度已达天文数字。以 2024 年数据为参考,全网难度超过 万亿,对应的 是一个有着大量前导零的 256 位整数,个人 CPU 或 GPU 已无出块可能。专业化、规模化的 ASIC 矿场主导了全网出块,普通人只能通过矿池(Mining Pool)按贡献算力比例分享收益。这验证了 PoW 体系的自我演进:随着经济价值提升,专用硬件必然出现,安全壁垒也随之升高。
本节要点
- 比特币每 2016 个区块(约 2 周)进行一次难度调整,核心公式为 ,单次调整比例被限制在 。
- 10 分钟出块时间是网络传播延迟与用户体验之间的工程权衡:太快必然分叉,太慢不可忍受。
- 难度调整是区块链协议的负反馈闭环:无论算力如何波动,都能将出块时间拉回 10 分钟。
4.5 比特币改进协议(BIP)与隔离见证
4.5.1 BIP 流程与分类体系
比特币作为一套去中心化的开源协议,其升级不能依靠某个单一机构拍板决策。为了将技术提案系统化、透明化,社区借鉴 IETF 的 RFC(Request for Comments)机制,设计了 BIP(Bitcoin Improvement Proposal,比特币改进建议) 流程。BIP 既是技术文档,也是政治协商工具——任何涉及共识规则、网络协议或应用程序接口的重大变更,都必须以 BIP 的形式公开讨论。
BIP 的编号分类
BIP 按性质分为三个类别,编号本身并无优先级,但社区关注度往往因内容而异:
| 类别 | 说明 | 典型示例 |
|---|---|---|
| Standards Track(标准类) | 直接影响共识规则、P2P 网络协议或交互标准,要求全节点兼容实现 | BIP-141(SegWit 激活规则)、BIP-143(签名验证规则) |
| Informational(信息类) | 提供技术说明、设计原理或最佳实践,不强制实现 | BIP-32(分层确定性钱包设计原理) |
| Process(流程类) | 描述比特币开发的工作流程、角色分工、BIP 的元流程 | BIP-2(BIP 自身及其修订的治理流程) |
BIP 生命周期
一个 BIP 从构想到落地,通常经历以下状态转换:
stateDiagram-v2
[*] --> 草案(Draft)
草案(Draft) --> 已接受(Accepted): 被编辑分配编号后进入社区评议
已接受(Accepted) --> 最终(Final): 技术标准已被实现并广泛部署
已接受(Accepted) --> 活跃(Active): 流程类 BIP 持续有效
草案(Draft) --> 推迟(Deferred): 长期无进展
已接受(Accepted) --> 废除(Withdrawn): 提案人主动撤回
最终(Final) --> 过时被(Superseded): 被后续 BIP 替代
活跃(Active) --> 过时(Superseded)
最终(Final) --> 废弃(Obsolete): 已部署但被完全淘汰
每份 BIP 必须包含以下要素:
- 动机(Motivation):为什么需要这个变更?
- 规范(Specification):精确的协议修改,可供开发者直接实现。
- 兼容性分析:向后还是向前兼容?旧节点会遇到什么行为?
- 参考实现(Reference Implementation):通常是 Bitcoin Core 的 pull request 链接。
BIP-141 / 143 / 144:里程碑级组合
隔离见证(SegWit)并非单一 BIP,而是一套协调部署的提案组合:
gantt
title SegWit 相关 BIP 里程碑(2015–2017)
dateFormat YYYY-MM
section 核心规则
BIP-141 :2015-12, 2017-08
BIP-143 :2016-01, 2017-08
BIP-144 :2016-01, 2017-08
section 部署机制
BIP-9 版本位信号 :2016-05, 2016-11
UASF(BIP-148)施压 :2017-05, 2017-08
- BIP-141:定义 SegWit 的共识层激活规则,包括版本位信号逻辑和新的区块 commitment 结构。
- BIP-143:定义 v0 版本的 Witness Program 的签名验证方式,引入更高效的 sighash 计算(统一使用序列化金额,避免重新序列化)。
- BIP-144:扩展 P2P 协议消息格式(
getdata、inv、block等),允许节点在兼容旧节点的同时传递含 witness 的交易与区块。
BIP-9:版本位(Version Bits)软分叉部署机制
在 SegWit 之前,软分叉通常采用固定区块高度激活(如 BIP-34、BIP-66)。这种方式存在风险:若矿工未提前升级,升级高度到达时可能产生孤块链分裂。BIP-9 引入 版本位信号(Version Bits):矿工在区块版本号的 32 位字段中设置特定位,表示已就绪支持某提案。当连续一个难度调整周期(2016 个区块,约 2 周)中信号比例超过阈值,该提案进入 锁定(Locked-in) 状态,再经过一个周期后自动 激活(Active)。
用 Python 描述阈值监控逻辑:
import math
# BIP-9 参数示例(对应 SegWit:第 1 位)
BIT_ID = 1 # 版本号中的第 1 位(从 0 开始)
THRESHOLD = 1916 # 一个 2016 区块周期内需要信号的最小区块数
RETARGET_PERIOD = 2016 # 难度调整周期长度
LOCKED_IN_DELAY = 2016 # 锁定后延迟激活的区块数
BLOCK_VERSION_TOP_BITS = 0x20000000 # 版本号基础前缀
def has_bip9_signal(version: int, bit_id: int) -> bool:
"""检查区块版本号是否对某位发出 BIP-9 信号。"""
# BIP-9 要求该位为 1,且前 3 位为 0x001(即基础掩码 0x20000000)
if (version & BLOCK_VERSION_TOP_BITS) != BLOCK_VERSION_TOP_BITS:
return False
return (version >> bit_id) & 1 == 1
def check_activation(signals: list[bool]) -> str:
"""
给定一个长度为 2016 的布尔序列,返回状态。
状态机:Defined -> Started -> LockedIn -> Active
"""
if len(signals) != RETARGET_PERIOD:
raise ValueError("序列长度必须等于难度调整周期")
count = sum(signals)
if count >= THRESHOLD:
# 进入锁定,下一个周期自动激活
return "LockedIn (will be Active after 1 more period)"
else:
# 信号不足,保持 Started 或回退
return f"Started (got {count}/{RETARGET_PERIOD}, need {THRESHOLD})"
# 示例:一个周期内 1916/2016 ≈ 95%
ratio = THRESHOLD / RETARGET_PERIOD
print(f"BIP-9 激活阈值: {THRESHOLD}/{RETARGET_PERIOD} = {ratio:.2%}")
# 模拟
example_signals = [True] * THRESHOLD + [False] * (RETARGET_PERIOD - THRESHOLD)
print(check_activation(example_signals))运行结果为:
BIP-9 激活阈值: 1916/2016 = 95.00%
LockedIn (will be Active after 1 more period)用公式表示阈值条件:
BIP-9 的优点在于 平滑过渡:矿工可以用算力投票表达就绪状态,而不会因为没有信号而立刻分叉。但 SegWit 的 BIP-9 部署在 2016–2017 年遇到了矿工信号不足的僵局,最终社区通过 UASF(User-Activated Soft Fork,BIP-148)——即普通全节点用户设定旗帜日(flag day)单方面拒绝不含信号的区块——施压,才迫使矿工在 2017 年 8 月达成共识激活 SegWit。
本节要点总结:BIP 是比特币协议治理的正式流程,分为标准、信息、流程三类。BIP-141/143/144 组合构成了 SegWit 的技术蓝图。BIP-9 通过版本位信号与 95% 阈值机制实现矿工驱动的平滑激活,但 SegWit 的历史也证明:在高度去中心化系统中,算力投票与用户意志的博弈是升级治理的核心张力。
4.5.2 隔离见证(SegWit)的技术本质
核心设计动机
传统比特币交易中,签名数据(scriptSig) 与交易金额、输入输出等核心数据被混在同一结构中。这种设计导致了三个相互关联的问题:
- 交易可锻性(Transaction Malleability):签名数据的微调会改变交易哈希(txid),破坏依赖 txid 的链下协议。
- 协议升级困难:签名验证逻辑与交易结构紧耦合,扩展新脚本类型需涉及全局共识规则。
- 区块容量瓶颈:签名占交易体积的约 60%,但旧规则对「交易数据」与「签名数据」一视同仁,无法灵活定价。
隔离见证的本质是 结构重组:将签名数据从交易体中剥离,移入一个独立的 witness(见证) 字段。如下对比:
graph LR
subgraph 传统交易结构
tx_in1["输入1:txid + vout + scriptSig(签名)"]
tx_in2["输入2:txid + vout + scriptSig(签名)"]
tx_out1["输出1:金额 + scriptPubKey"]
tx_out2["输出2:金额 + scriptPubKey"]
end
subgraph SegWit 交易结构
sw_in1["输入1:txid + vout + (空/极简脚本)"]
sw_in2["输入2:txid + vout + (空/极简脚本)"]
sw_out1["输出1:金额 + scriptPubKey(包含 witness program)"]
sw_out2["输出2:金额 + scriptPubKey"]
w1["Witness 1:签名 + 公钥"]
w2["Witness 2:签名 + 公钥"]
end
style tx_in1 fill:#fdd
style tx_in2 fill:#fdd
style w1 fill:#dfd
style w2 fill:#dfd
红底部分为旧结构中包含签名的位置,绿底为 SegWit 新增的独立 witness 字段。注意 witness 不在交易主体内,而是在序列化时附加于每个输入之后。
两种交易 ID 的区分
SegWit 引入了两套哈希标识,解决可锻性的同时保持向后兼容:
| 标识符 | 覆盖范围 | 用途 |
|---|---|---|
| txid | 交易的非 witness 部分(即传统字段) | 旧节点可见,用于链上 txid 索引、区块内的 Merkle 树 |
| wtxid | 完整交易(含 witness 数据) | 新节点间同步完整交易,用于 witness 的 Merkle 树根校验 |
用公式精确描述:
其中 dSHA256 表示双重 SHA-256 运算:。
这意味着:即使攻击者修改了 witness 中的签名字节,txid 仍然保持不变,而 wtxid 会变化。依赖 txid 的链下合约因此获得稳定性。
数据结构变化示例
以一笔 P2PKH 交易与对应的 P2WPKH(SegWit Pay-to-Witness-Public-Key-Hash)交易做 raw hex 拆解对比:
传统 P2PKH 输入结构(hex 概念拆解):
[txid, 32 bytes] [vout, 4 bytes] [scriptSig 长度, varint] [scriptSig, ~106 bytes]
|
└── 签名 (~72 bytes) + 公钥 (~33字节) 全部挤在 scriptSig 中P2WPKH 输入结构(hex 概念拆解):
[txid, 32 bytes] [vout, 4 bytes] [scriptSig 长度 = 0x00] <-- 空 scriptSig!
[Witness 计数, varint] [签名, ~72 bytes] [公钥, ~33 bytes] <-- 签名移入 witness在输出端,P2WPKH 使用一种新的 scriptPubKey,格式为:
0x00 (OP_0, 表示 v0 witness program) [0x14 (20字节)] [20-byte 公钥哈希]旧节点看到 OP_0 <20-byte push> 时,将其视为 "任何人可花费"(anyone-can-spend) 输出——即只要提供一个使顶部栈非零的脚本即可花费。新节点则按 SegWit 规则要求对应的 witness 数据必须包含有效的签名与公钥。
向后兼容机制
旧节点只解析到传统交易字段时会看到:输入的 scriptSig 为空,输出的 scriptPubKey 是 OP_0 + 20 字节。从旧节点的视角,这笔交易满足「脚本执行成功」(空 scriptSig 导致堆栈中有一个 OP_0,非零,验证通过),因此会被视为有效并进入区块。然而旧节点不能验证 witness 签名的真伪——这在 SegWit 激活后的软分叉规则下不是问题,因为一旦绝大多数算力已升级,包含无效 witness 的区块会被新节点拒绝,进而在最长链竞争中落败。
graph TD
A[旧全节点<br/>不识别 witness] -->|看到 P2WPKH 输出| B[认为 "anyone-can-spend"]
A -->|看到空 scriptSig| C[认为输入有效]
D[新全节点<br/>识别 witness] -->|要求完整 witness| E[验证签名真伪]
D -->|见证验证失败| F[拒绝区块/交易]
E -->|最长链共识| G[无效交易无法被包含进有效链]
B -.->|旧节点沿用旧规则,<br/>仍承认最长链| G
这就是 软分叉 的精髓:旧规则的有效集与新规则的有效集保持前向兼容——新规则是旧规则的 子集收紧。
本节要点总结:SegWit 的核心创新是结构重组:将签名数据从交易体移入独立的 witness 字段。通过区分 txid 与 wtxid,它既修复了可锻性,又为未来脚本扩展(如 Taproot)预留了版本化空间。旧节点将 witness 输出误识为 "anyone-can-spend",恰好实现了无需强制升级的向后兼容性,是协议工程中结构分层思想的典范。
4.5.3 交易可锻性(Transaction Malleability)漏洞与修复
漏洞本质
交易可锻性是指:攻击者(包括矿工、中继节点甚至原始签名者自己)可以在不改变交易语义(即不改变谁付给谁、金额多少)的前提下,改变交易的序列化表示,从而改变其 txid。
为什么签名数据会导致这种脆弱性?原因在于 DER(Distinguished Encoding Rules)编码的签名 允许某些等价变体。ECDSA 签名 在序列化时:
- 整数 和 在 DER 编码中如果最高位为 1,必须 padding 一个
0x00字节以避免被误解析为负数。 - 如果签名校验库(如不同版本的 OpenSSL)对 padding 的处理不一致,同样的有效签名可以有不同的字节表示。
- 更严重地,第三方可以将 替换为 (即取膜运算下的逆元),两者都是合法签名。
触发路径示例
以下 Python 示例演示:对同一底层内容,仅改变外层序列化包装,SHA-256 哈希将完全不同:
import hashlib
def double_sha256(data: bytes) -> str:
return hashlib.sha256(hashlib.sha256(data).digest()).hexdigest()
# 假设一笔简化的交易核心数据(不含签名部分)
base_tx = bytes.fromhex(
"0100000001" # 版本 + 输入计数
"aabbccdd" * 8 + # 32-byte 前笔交易 txid(占位)
"00000000" # vout
"00" # scriptSig 长度 = 0(SegWit 场景)
"ffffffff" # sequence
"01" # 输出计数
"00f2052a0100000000" # 输出金额(50 BTC)
"19" # scriptPubKey 长度
"76a914" + "11" * 20 + "88ac" # P2PKH 脚本
"00000000" # 锁定时间
)
# 两种"语义等价"但字节不同的签名序列化变体
# 变体 A:正常 DER 编码的签名
sig_A = bytes.fromhex(
"3045022100" + "aa" * 32 + "0220" + "bb" * 32 # 标准 r, s 长度
)
# 变体 B:在 r 前增加一位 0x00 会导致 DER 解析器仍接受,
# 实际中更复杂的 malleability 包含 SIGHASH 字节重排和其他合法变体
sig_B = bytes.fromhex(
"3046022100" + "00" + "aa" * 31 + "022100" + "bb" * 32 # 对 r 做 0x00 填充改写
)
# 在传统交易中,scriptSig = 签名 + 公钥
tx_A = base_tx[:len(base_tx)-24] + bytes([len(sig_A) + 33]) + sig_A + bytes([33]) + bytes.fromhex("cc"*33) + base_tx[-24:]
tx_B = base_tx[:len(base_tx)-24] + bytes([len(sig_B) + 33]) + sig_B + bytes([33]) + bytes.fromhex("cc"*33) + base_tx[-24:]
print(f"txid A (变体A): {double_sha256(tx_A)}")
print(f"txid B (变体B): {double_sha256(tx_B)}")
# 关键问题:base_tx 不含签名时计算的 txid(即 SegWit 后的 txid)
segwit_txid = double_sha256(base_tx)
print(f"SegWit txid (不含签名): {segwit_txid}")输出(示例性):
txid A (变体A): 1a2b3c4d...
txid B (变体B): 5e6f7a8b...
SegWit txid (不含签名): 9c8d7e6f...虽然是高度简化的演示,但它揭示核心矛盾: 当 ,但两者语义等价时。公式化表示为:
其中 表示交易语义(输入指向、输出金额、锁定脚本), 表示交易哈希。
危害场景
可锻性对链下协议是致命的。考虑一个简单的 支付通道(Payment Channel):
- Alice 创建一笔未签名交易 ,将资金锁定到 2-of-2 多签地址,需要 Alice 与 Bob 共同签名才能花费。
- Alice 签名并广播 。
- Alice 在本地记录 。
- 矿工 Mallory 收到 ,轻微修改 scriptSig,得到 ,语义不变但 。
- Mallory 广播 ,它更早被打包进区块。
- Alice 发现原 txid 从未被确认。她依赖 构造的第二笔退款交易或状态承诺交易,全部失效——因为它们引用了不存在的 txid 。
sequenceDiagram
participant A as Alice
participant M as 网络/矿工
participant B as Bob
A->>M: 广播 T₁ (txid=X)
M->>M: 篡改 scriptSig → T₁' (txid=Y)
M->>B: 广播 T₁' 并打包
A->>A: 等待 X 确认… 无果
A->>A: 依赖 X 的后续交易全部失效!
闪电网络中的 HTLC(Hash-Time-Locked Contract)同样依赖固定的 txid 构建链下原子交换路径。可锻性若未被修复,任何中间人都能通过不断锻打 txid 来破坏通道状态机的一致性。
历史背景:Mt.Gox 事件
2014 年初,曾经世界最大的比特币交易所 Mt.Gox 宣布破产,声称因「交易可锻性攻击」损失约 85 万枚比特币。后续调查表明,Mt.Gox 自身的系统缺陷(如热钱包管理混乱、未做适当账务核对)才是主因,但可锻性确实给了攻击者可乘之机——攻击者向 Mt.Gox 提款后,监听网络中的原交易,锻打 txid 后广播变体,导致 Mt.Gox 的追踪系统认为原交易失败并重复发放提款。无论 Mt.Gox 的最终真相如何,该事件将可锻性修复的紧迫性推向了社区共识的中心。
SegWit 如何彻底修复
SegWit 的修复逻辑简洁而根本:既然签名数据是 malleability 的根源,那就让 txid 不覆盖签名。
其中 为交易非 witness 部分。攻击者再怎么锻打 witness,txid 稳如磐石。链下合约可以安全地引用 txid,而 P2P 层对新旧节点分别传输 wtxid 或不含 witness 的交易,也不影响兼容性。
本节要点总结:交易可锻性的本质是签名序列化的非唯一性导致 txid 可被无意义地改变,进而使依赖 txid 的链下合约失效。Mt.Gox 事件催化了修复共识。隔离见证通过将签名移出 txid 计算路径,从结构上根除了这一攻击向量,为闪电网络等 layer-2 协议奠定了安全基础。
4.5.4 SegWit 后的 Weight 单位与区块容量新定义
从固定字节到加权计量的转变
隔离见证不仅改变了数据结构,还重新定义了区块容量限制,创造了一个巧妙的变相扩容机制。
在 SegWit 激活前,比特币的硬约束是:
所有数据(无论签名还是金额)按 1:1 计入该上限。SegWit 引入了 Weight(权重)这一新的计量单位:
| 数据类型 | 计价比例 |
|---|---|
| 非见证数据(Base,如 txid、vout、金额、输出的 scriptPubKey) | 4 weight/byte |
| 见证数据(Witness,如签名、公钥) | 1 weight/byte |
总体限制变为:
这相当于说:若一个区块完全不含 witness 数据,它的最大基础大小仍然约为 1 MB(),与旧规则一致。但若区块中塞入大量 witness 数据,实际可容纳的字节总量可以远超 1 MB,最高可达约 4 MB(如果整个区块全是 witness,极端情况)。实际中由于交易不可能没有 base 数据,SegWit 区块的典型大小约为 1.6–2.3 MB。
pie title 1 MB 传统区块 vs SegWit 区块数据构成对比(虚构示例)
"Base 数据(费用权重: 4x)" : 800000
"Witness 数据(费用权重: 1x)" : 800000
"剩余容量" : 2400000
上图是概念性的:一个 weight = 4,000,000 的区块,base 占 800,000 bytes(贡献 3,200,000 weight),witness 占 800,000 bytes(贡献 800,000 weight),合计恰好 4,000,000。此时总字节量 = 1.6 MB,但 fee 市场按 weight 定价。
Virtual Size(vsize)与手续费率
为了让钱包和用户有一个直观的单位,社区引入了 virtual size(vsize):
vsize 的单位是 虚拟字节(vB)。例如一笔交易的 weight 为 892,则 vsize = 223 vB。矿工和手续费估算工具普遍使用 sat/vB(每虚拟字节聪)作为费率单位:
实际计算示例
import math
def segwit_metrics(base_size: int, witness_size: int, fee_sat: int) -> dict:
"""
计算 SegWit 交易/区块的 weight、vsize 与费率。
"""
weight = base_size * 4 + witness_size * 1
vsize = math.ceil(weight / 4) # 向上取整,与 Bitcoin Core 一致
fee_rate = fee_sat / vsize if vsize > 0 else 0
total_bytes = base_size + witness_size
return {
"base_size": base_size,
"witness_size": witness_size,
"total_bytes": total_bytes,
"weight": weight,
"vsize": vsize,
"fee_sat": fee_sat,
"fee_rate_sat_vb": round(fee_rate, 2),
"base_data_limit_if_full_weight": 1000000 # 若全为 base 数据
}
# 典型 P2WPKH 交易(1 输入,2 输出)
tx1 = segwit_metrics(base_size=82, witness_size=108, fee_sat=1000)
print("典型 P2WPKH 交易(1输入2输出):")
for k, v in tx1.items():
print(f" {k}: {v}")
print()
# 一个理论上的 SegWit 区块(假设满 weight)
block = segwit_metrics(base_size=250_000, witness_size=3_000_000, fee_sat=1_000_000)
print("理论最大 witness 利用区块:")
for k, v in block.items():
if k == "fee_rate_sat_vb":
continue # 区块级别不按单笔费率展示
print(f" {k}: {v:,}")
# 吞吐量对比
traditional_block_tx_count = 1_000_000 / 250 # 传统 250B/tx 平均
segwit_block_tx_count = 4_000_000 / (82*4 + 108) # 使用上方 P2WPKH 的 weight
print(f"\n传统 1MB 区块理论交易数(250B/tx): ~{int(traditional_block_tx_count)}")
print(f"SegWit 满 weight 区块交易数(同上交易结构): ~{int(segwit_block_tx_count)}")输出结果:
典型 P2WPKH 交易(1输入2输出):
base_size: 82
witness_size: 108
total_bytes: 190
weight: 436
vsize: 109
fee_sat: 1000
fee_rate_sat_vb: 9.17
base_data_limit_if_full_weight: 1000000
理论最大 witness 利用区块:
base_size: 250,000
witness_size: 3,000,000
total_bytes: 3,250,000
weight: 4,000,000
vsize: 1,000,000
fee_sat: 1,000,000
传统 1MB 区块理论交易数(250B/tx): ~4000
SegWit 满 weight 区块交易数(同上交易结构): ~9174矿工激励的微妙变化
Weight 计量改变了矿工的包块策略。对于相同的用户手续费,一笔高 witness 占比的交易 weight 更低,意味着矿工可以在同样的 4M weight 限制下打包更多此类交易,从而收取更多总手续费。这从经济层面激励了钱包和交易所迁移至 SegWit 地址。
用表格对比传统交易与 SegWit 交易的 fee 效率:
| 交易类型 | base (B) | witness (B) | weight | vsize | 同样 1000 sat 的费率 |
|---|---|---|---|---|---|
| P2PKH(传统) | 192 | 0 | 768 | 192 | 5.21 sat/vB |
| P2WPKH(原生 SegWit) | 82 | 108 | 436 | 109 | 9.17 sat/vB |
注意表中的费率差异是 相同绝对手续费下的费率表现:SegWit 交易因为 vsize 更小,按 sat/vB 计价的"单价"看起来更高,但由于大小减小,用户实际支付的总手续费可以更少。钱包界面通常显示「预估手续费 500 sat」——在 SegWit 下,这 500 sat 对应的费率(sat/vB)更高,因而更容易被矿工优先打包。
本节要点总结:SegWit 用 weight 单位重新定义了区块容量:非 witness 数据按 4:1 计价,witness 按 1:1 计价,上限 4M。这变相将实际区块字节容量提升到约 1.6–2.3 MB,同时保留了 1 MB 的向后兼容表面。vsize = weight / 4 是钱包与矿池通用的手续费度量单位,高 witness 占比的交易在 weight 体系中更"轻",形成经济激励推动地址格式迁移。
4.6 本章小结
经过前面章节对 UTXO 模型、PoW 共识、比特币脚本与虚拟机,以及 BIP / 隔离见证的系统性探讨,本章提炼出三个贯穿比特币设计的底层认知。
4.6.1 关键认知一:UTXO = 现金模型,而非账户余额
在以太坊、传统银行等账户模型中,系统维护的核心状态是「Alice 有多少余额,Bob 有多少余额」。交易即「从 Alice 的余额减去 5,向 Bob 的余额加上 5」。
比特币的 UTXO 模型则不同:它不追踪余额,而是追踪 一张张尚未被花掉的「币券」(即 UTXO)。你钱包里显示的「余额 2.5 BTC」,实质上是:
钱包扫描全链所有输出,筛选出 scriptPubKey 能被你的私钥解锁的那些,加总它们的金额。余额是一个派生值,而非链上原生存储的状态。
flowchart LR
subgraph 账户模型
A1[Alice: 余额 10 BTC] --> T1[交易: -3 BTC]
T1 --> A2[Alice: 7 BTC]
T1 --> B1[Bob: +3 BTC]
end
subgraph UTXO 模型
U1["UTXO₁ (5 BTC, Alice)
UTXO₂ (5 BTC, Alice)"] --> T2["交易
输入: UTXO₁ + UTXO₂
输出: 3 BTC → Bob
6.9 BTC → Alice(找零)
0.1 BTC → 矿工费"]
T2 --> U3["UTXO₃ (3 BTC, Bob)
UTXO₄ (6.9 BTC, Alice)"]
end
这种现金模型带来了三个深层影响:
- 隐私增强:不同 UTXO 之间没有显式关联。外人看到的是一个个独立输出,而非一个全局可见的「Alice 账户」。虽然启发式聚类分析(common-input-ownership 等)能推断地址归属,但 UTXO 模型至少提供了不默认公开账户关联的基线。
- 并发天然支持:Alice 持有 UTXO₁ 与 UTXO₂,可以分别交给两个不同的交易对手同时签名两笔交易,无需担心 nonce 顺序冲突。账户模型中,同一账户连续发多笔交易必须严格管理 nonce 递增,这在高并发场景下是工程痛点。
- 找零管理:如同用 10 元买 3 元商品后收到 7 元找零,UTXO 交易必然产生找零输出。钱包的 coin selection 算法(如 Branch-and-Bound)需要在隐私(减少找零暴露)与手续费效率之间做权衡,这是账户模型中没有的复杂度。
本节要点总结:UTXO 不是余额,是一堆可花费的币券。余额是派生量,现金模型带来天然并发与更强的隐私基线,但也引入了找零管理与 coin selection 的复杂度。
4.6.2 关键认知二:PoW 拍卖的是不可逆的时间成本
经济学意义上,PoW 不是「解题」,而是 用物理世界不可逆的资源消耗,购买数字世界不可逆的时间戳排序权。
每一次出块,矿工消耗的电力与硬件折旧一旦付出,便无法回收。这种不可逆性是 PoW 安全的根基——攻击者若要重写历史,不能「回到过去」节省已消耗的能源,而必须在当前时间点 重新支付等量的物理成本:
而且这笔成本必须在攻击窗口内持续跑赢诚实算力的累积。中本聪在白皮书附录中给出的攻击成功概率公式为:
更简洁的理解是:设攻击者算力占比为 ,诚实算力占比为 ,则攻击者追赶 个确认区块的成功概率随 指数衰减。重写 个区块的期望成本约为:
graph TD
subgraph 成本维度
D1[区块深度 N] --> E1[重写成本指数增长]
D2[攻击者算力 q] --> E2[若 q > 50% 理论可行但极昂贵]
end
E1 --> F1[6 确认 ≈ 足够安全]
E2 --> F2[全球算力竞争使攻击不经济]
时间不可逆性在现实世界中司空见惯——泼出去的水、燃烧的煤、耗散的熵。但在数字系统中,复制与回溯近乎零成本,使得「不可逆的时间」成为稀缺资源。PoW 的巧妙之处正是将物理世界的不可逆熵增桥接到数字世界,使区块链历史获得了类似石刻的难以篡改性。
从安全预算角度看,PoW 的「浪费」论调忽视了替代方案的成本:
- 维护一套全球金融清算系统(SWIFT、央行系统、律师与审计成本)的年开销绝不低廉。
- PoW 用市场化的电力竞价,实现了无需许可的准入与去中心化的安全预算分配。
本节要点总结:PoW 消耗的不是无意义的算力,而是物理世界不可逆的成本。重写历史意味着重新支付全部已消耗的能量,且必须在窗口期内跑赢诚实网络。PoW 用熵增换共识,是廉价而稳固的去中心化安全预算。
4.6.3 关键认知三:SegWit 是协议升级中软分叉的典范
软分叉与硬分叉的根本差异可以用集合论精确刻画:
flowchart
subgraph 软分叉
direction LR
O1["旧规则有效集\nValid_old"] --> N1["新规则有效集\nValid_new\n⊂ Valid_old"]
style N1 fill:#dfd
end
subgraph 硬分叉
direction LR
O2["旧规则有效集\nValid_old"] -- 不重叠 --> N2["新规则有效集\nValid_hard"]
style O2 fill:#fdd
style N2 fill:#fdd
end
软分叉的美妙之处在于旧节点不会拒绝新规则下产生的区块——因为新规则更严格,旧节点眼里的有效区块仍然是新规则下的有效区块。新节点则会额外拒绝一些旧规则允许但新规则禁止的行为,逐步收紧边界。
SegWit 的软分叉实现堪称艺术:
- 旧节点视角:P2WPKH 输出的 scriptPubKey 是
OP_0 <20-byte>,旧节点将其解释为 anyone-can-spend。因此旧节点看到一笔花掉该输出的交易时,只要脚本执行不报错(空 scriptSig 导致堆栈非零),就认为有效。 - 新节点视角:同一笔交易被按新规则解析,要求输入的 witness 字段提供有效的签名与公钥,验证哈希是否匹配。
旧节点「误解」了输出的含义,却恰好因为这种误解而继续承认链的有效性——这是精心设计的 语义重载(semantic overloading)。类似地,区块中的 witness 数据通过 coinbase 交易中的 witness commitment(一个特殊的输出承诺 wtxid Merkle 树根)被锚定,旧节点不解析该输出,新节点通过它确保 witness 没有被篡改。
| 分叉类型 | 网络影响 | 典型案例 |
|---|---|---|
| 软分叉 | 链不分叉,旧节点仍可同步最长链(但不验证新规则细节) | SegWit(2017)、Taproot(2021) |
| 硬分叉 | 必然分裂为两条链,除非全网 100% 升级 | ETH/ETC 分裂(2016) |
对比以太坊 DAO 事件后的硬分叉,SegWit 保持了网络和账本的连续性,没有制造永久性社区分裂。
从 BIP-9 到 Speedy Trial
SegWit 的部署过程(BIP-9 信号 + UASF 施压)为后续协议升级提供了宝贵先例。2021 年 Taproot 的激活采用了改良后的 BIP-341 Speedy Trial 机制:缩短信号窗口、快速失败或锁定,减少社区僵局。从政治角度,SegWit 之争让比特币社区认识到:用户节点运行者的意志(UASF)在治理博弈中具有与矿工算力相抗衡的筹码;从技术角度,SegWit 证明了复杂协议升级完全可以在不分裂链的情况下完成。
本节要点总结:软分叉是新规则收紧旧规则的有效子集,SegWit 通过 witness 的 "anyone-can-spend" 语义重载,让旧节点在无感知的情况下继续认可新链,实现了无分裂升级。相比硬分叉,软分叉维护了网络连续性。SegWit/BIP-9/UASF 的合作为后续 Taproot 的 Speedy Trial 机制提供了政治与技术双重先例。
本章小结
回顾本章,我们需要带走的6个关键认知:
- UTXO = 现金,不是账户余额。 比特币账本上不存在“张三有5 BTC”这样的记录,只有一个个未花费的交易输出。你的钱包余额仅仅是所有属于你的UTXO面值的总和。理解这一点,是理解后续脚本系统、交易构造和隐私模型的根基。
- PoW 拍卖的是不可逆的时间成本。 中本聪并没有发明一种“数学上不可攻破”的系统,而是通过PoW + 最长链 + 激励的三位一体设计,让“重写历史”在经济上不可持续。比特币的安全是经济安全,不是绝对密码学。
- 脚本演进体现了协议工程的智慧。 从P2PK到Taproot,每一次升级都在不改变底层数据结构的前提下,用软分叉实现了更强的隐私、更低的费用、更好的扩展性。锁定脚本与解锁脚本的对偶结构,是UTXO可编程性的核心杠杆。
- PoW 不是“浪费”,而是“用物理成本购买分布式共识的安全”。每一次哈希运算都在为区块提供不可伪造的成本证明,攻击者必须付出更高的成本才能重写历史。
- 挖矿 = 暴力搜索 + 难度目标。不是一个需要“聪明才智”的解题过程,而是一个在巨大空间中海选幸运数字的概率过程。难度(Difficulty)与目标值(Target)成反比:。
- 难度调整是比特币协议的“自动调压阀”。通过每 2016 个区块重新评估并调整目标值,PoW 网络在完全开放的算力环境中维持了 10 分钟出块时间的惊人稳定性。理解这一负反馈机制后,我们将进入第 5 章——在更广阔的共识算法谱系中,将 PoW 与 PoS(权益证明)、PBFT 等机制进行系统对比,进一步理解分布式共识的设计空间与权衡之道。
此外,本章从 UTXO 到 PoW,从脚本虚拟机到 BIP 治理与隔离见证,呈现了一条由三个底层认知贯穿的脉络:
- UTXO 是现金,不是账户——它带来并发与隐私,但也带来找零与 coin selection 的工程挑战。
- PoW 是时间的物理锚定——不可逆的熵增支出,换取不可逆的共识历史。
- SegWit 是软分叉的典范——用结构重组与语义重载,在不分叉的前提下修复可锻性、变相扩容、并为未来协议升级铺平道路。
比特币的协议演进不是平滑的,SegWit 从 2015 年提案到 2017 年激活历时两年,期间社区经历了激烈的路线之争。但正是这种去中心化的博弈与妥协,使得最终落网的规则改动具备了广泛的社会共识与坚实的技术基础。隔离见证不仅是一项区块扩容技术,更是比特币治理哲学的一次成功实践:在没有人拥有「重启键」的系统中,升级必须以不抛弃旧节点为前提,softly, but surely.
评论
0评论加载中…